/** * 图片上传组件 * 基于 Element Plus的上传组件封装 * 支持图片预览、删除、大小限制、尺寸限制等功能
*/
<template>
  <div class="pro-upload-img-box">
    <div class="pro-upload-img-content">
      <!-- 已上传图片列表 -->
      <div class="upload-img-card" v-for="(item, index) in fileList" :key="index">
        <!-- 图片预览 -->
        <el-image
          class="img-sty"
          :preview-src-list="[item.url]"
          fit="cover"
          :src="item.url"
          alt=""
        />
        <!-- 删除按钮 -->
        <el-image
          v-if="!disabled"
          src="https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/img-close.png"
          class="img-close"
          @click="handleRemove(item, index)"
        />
        <!-- 图片遮罩层 -->
        <div class="img-mask">
          <el-image
            src="https://static.wxb.com.cn/frontEnd/images/ideacome-vue3-component/img-preview.png"
            class="img-preview"
          />
        </div>
      </div>
      <!-- 上传组件 -->
      <el-upload
        v-loading="loading"
        ref="proUploadImgRef"
        :class="['pro-upload-img', { 'is-disabled': disabled }]"
        v-bind="uploadProps"
        :before-upload="beforeUpload"
        :on-success="handleSuccess"
        :on-error="handleError"
        :on-exceed="handleExceed"
      >
        <slot>
          <div class="upload-card">
            <el-icon class="upload-icon">
              <Plus />
            </el-icon>
            <div v-if="uploadText" class="upload-text">
              {{ uploadText }}
            </div>
          </div>
        </slot>
      </el-upload>
    </div>
    <!-- 提示信息 -->
    <slot name="tip">
      <div class="upload-tip" v-if="tip">
        {{ tip }}
      </div>
    </slot>
  </div>
</template>

<script setup name="ProUploadImg">
  import { ref, computed } from 'vue'
  import { Plus } from '@element-plus/icons-vue'
  import { ElMessage } from 'element-plus'

  // Props 定义
  const props = defineProps({
    /** 上传地址 */
    action: {
      type: String,
      required: true
    },
    /** 请求头 */
    headers: {
      type: Object,
      default: () => ({})
    },
    /** 是否支持多选 */
    multiple: {
      type: Boolean,
      default: false
    },
    /** 最大上传数量，0表示不限制 */
    limit: {
      type: Number,
      default: 0
    },
    /** 接受的文件类型，如：.jpg,.png,.jpeg */
    accept: {
      type: String,
      default: '.jpg,.png,.jpeg'
    },
    /** 文件大小限制 */
    maxSize: {
      type: Number,
      default: 0
    },
    /** 文件大小单位（KB/MB） */
    sizeUnit: {
      type: String,
      default: 'MB',
      validator: (value) => ['KB', 'MB'].includes(value)
    },
    /** 图片宽度限制 */
    width: {
      type: Number,
      default: 0
    },
    /** 图片高度限制 */
    height: {
      type: Number,
      default: 0
    },
    /** 上传提示文字 */
    uploadText: {
      type: String,
      default: '上传图片'
    },
    /** 上传提示说明 */
    tip: {
      type: String,
      default: ''
    },
    /** 是否禁用 */
    disabled: {
      type: Boolean,
      default: false
    }
  })
  /** 初始文件列表 */
  const fileList = defineModel('fileList', {
    type: Array,
    default: () => []
  })

  // 事件定义
  const emit = defineEmits(['success', 'error', 'exceed', 'remove'])

  const proUploadImgRef = ref()
  const loading = ref(false)

  const uploadProps = computed(() => ({
    action: props.action,
    accept: props.accept,
    limit: props.limit,
    multiple: props.multiple,
    listType: 'picture-card',
    showFileList: false,
    headers: props.headers,
    fileList: fileList.value,
    disabled: props.disabled
  }))

  /**
   * 验证图片尺寸是否符合要求
   * @param {number} width - 图片宽度
   * @param {number} height - 图片高度
   * @returns {boolean} 是否符合要求
   */
  const validateImageSize = (width, height) => {
    if (props.width && props.height) {
      return width === props.width && height === props.height
    }
    if (props.width) {
      return width === props.width
    }
    if (props.height) {
      return height === props.height
    }
    return true
  }

  /**
   * 上传前校验
   * @param {File} file - 待上传的文件
   * @returns {Promise<boolean>} 是否通过校验
   */
  const beforeUpload = async (file) => {
    // 校验文件类型
    const fileTypeList = props.accept.split(',').map((item) => item.replace('.', ''))
    const fileType = file.name.split('.').pop()

    if (!fileTypeList.includes(fileType)) {
      ElMessage({
        message: `仅支持 ${fileTypeList.join('、')} 格式`,
        type: 'warning'
      })
      return false
    }

    // 校验文件大小
    if (props.maxSize) {
      const fileSize = file.size / 1024
      const maxSizeInKB = props.sizeUnit === 'MB' ? props.maxSize * 1024 : props.maxSize
      if (fileSize > maxSizeInKB) {
        ElMessage({
          message: `大小不能超过 ${props.maxSize}${props.sizeUnit}！`,
          type: 'warning'
        })
        return false
      }
    }

    // 校验图片尺寸
    return new Promise((resolve, reject) => {
      const img = new Image()
      img.src = URL.createObjectURL(file)
      img.onload = () => {
        URL.revokeObjectURL(img.src)
        const { width, height } = img

        if (!validateImageSize(width, height)) {
          const message =
            props.width && props.height
              ? `图片尺寸必须为 ${props.width}x${props.height}`
              : props.width
                ? `图片宽度必须为 ${props.width}px`
                : `图片高度必须为 ${props.height}px`

          ElMessage({
            message,
            type: 'warning'
          })
          reject(false)
          return
        }
        loading.value = true
        resolve(true)
      }
      img.onerror = () => {
        URL.revokeObjectURL(img.src)
        ElMessage({
          message: '图片加载失败',
          type: 'error'
        })
        reject(false)
      }
    })
  }

  /**
   * 上传成功回调
   * @param {Object} response - 服务器响应数据
   * @param {Object} uploadFile - 上传文件对象
   * @param {Array} uploadFiles - 上传文件列表
   */
  const handleSuccess = (response, uploadFile, uploadFiles) => {
    loading.value = false
    if (response.code === 0) {
      fileList.value.push(response.data)
    } else {
      proUploadImgRef.value.handleRemove(uploadFile)
      ElMessage({
        message: response.msg || response.message || '上传失败',
        type: 'error'
      })
    }
    emit('success', response, uploadFile, uploadFiles)
  }

  /**
   * 上传失败回调
   * @param {Error} error - 错误信息
   * @param {Object} uploadFile - 上传文件对象
   * @param {Array} uploadFiles - 上传文件列表
   */
  const handleError = (error, uploadFile, uploadFiles) => {
    loading.value = false
    ElMessage({
      message: '上传失败',
      type: 'error'
    })
    emit('error', error, uploadFile, uploadFiles)
  }

  /**
   * 超出限制回调
   * @param {Array} files - 超出限制的文件列表
   * @param {Array} uploadFiles - 已上传的文件列表
   */
  const handleExceed = (files, uploadFiles) => {
    ElMessage({
      message: `最多只能上传 ${props.limit} 张图片`,
      type: 'warning'
    })
    emit('exceed', files, uploadFiles)
  }

  /**
   * 移除图片
   * @param {Object} file - 要移除的文件对象
   * @param {number} index - 文件索引
   */
  const handleRemove = (file, index) => {
    fileList.value.splice(index, 1)
    proUploadImgRef.value.handleRemove(file)
    emit('remove', file)
  }
</script>

<style lang="scss" scoped>
  .pro-upload-img-box {
    .pro-upload-img-content {
      display: flex;
      flex-wrap: wrap;
      // 已上传图片卡片样式
      .upload-img-card {
        position: relative;
        width: 100px;
        height: 100px;
        margin: 0 12px 12px 0;
        // 图片样式
        .img-sty {
          width: 100%;
          height: 100%;
          overflow: hidden;
          border-radius: 6px;
        }
        // 删除按钮样式
        .img-close {
          position: absolute;
          top: -6px;
          right: -6px;
          z-index: 2;
          width: 20px;
          height: 20px;
          cursor: pointer;
        }
        // 遮罩层样式
        .img-mask {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          pointer-events: none;
          background: rgb(0 0 0 / 30%);
          border-radius: 6px;

          .img-preview {
            position: absolute;
            right: 8px;
            bottom: 8px;
            width: 20px;
            height: 20px;
            pointer-events: none;
          }
        }
      }

      // 禁用状态样式
      .is-disabled {
        :deep(.el-upload--picture-card) {
          cursor: not-allowed;
        }
      }
      // 上传按钮样式
      .pro-upload-img {
        margin-bottom: 12px;

        .upload-card {
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;

          .upload-icon {
            font-size: 20px;
            line-height: 100px;
            color: #8c939d;
            text-align: center;
          }

          .upload-text {
            margin-top: 10px;
            font-size: 14px;
            line-height: 24px;
            color: #8c939d;
            text-align: center;
          }
        }
      }

      // 上传组件样式覆盖
      :deep(.el-upload--picture-card) {
        width: 100px;
        height: 100px;
        background-color: #fff;
      }

      :deep(.el-upload-list__item) {
        width: auto;
        height: auto;
        overflow: visible;
      }
    }
    // 提示文字样式
    .upload-tip {
      font-size: 12px;
      color: #909399;
    }
  }
</style>
